#include "stdafx.h"

#include "Game.h"

const int predefinedLevelsCount = 3;
const char *predefinedLevel[predefinedLevelsCount] =
{
	"\x05\x05" "\x03" "BBBDDB",
	"\x05\x07" "\x04" "BDCBCFDB",
	"\x07\x08" "\x06" "BDCCDDCGEGFB"
	//"\x05\x05" "\x03" "BBBDDB",
	//"\x05\x05" "\x03" "BBBDDB"
};

Field::Field(Game *game):
	m_game(game),
	m_width(0),
	m_height(0),
	m_field(0),
	m_sel_x(-1),
	m_sel_y(-1),
	m_size(0),
	m_screen_width(0),
	m_screen_height(0),
	m_last_x(-1),
	m_last_y(-1),
	m_level_id(-1),
	m_level_remains(-1),
	m_level_x_count(-1)
{
	std::srand(GetTickCount());
	reset();
	loadPredefinedLevel(0);
}

void
Field::reset()
{
	m_sel_x = m_sel_y = -1;
	m_last_x = m_last_y = -1;
	m_level_remains = m_level_x_count;
}

void
Field::allocate(int w, int h)
{
	if (m_field && w == m_width && h == m_height)
	{
		fill(Field::Empty);
		return;
	}
	m_width = w;
	m_height = h;
	if (m_field)
	{
		delete[] m_field;
	}
	m_field = new char[w * h];
	fill(Field::Empty);
	reset();
}

int
sign(int x)
{
	if (x == 0)
		return 0;
	if (x > 0)
		return 1;
	return -1;
}

int
distance(int x, int y)
{
	const int ax = x > 0 ? x : -x;
	const int ay = y > 0 ? y : -y;
	return ax > ay ? ax : ay;
}

bool
Field::go(int x, int y)
{
	if (x < 0 || x >= m_width || y < 0 || y >= m_height)
		return false;
	if (m_last_x >= 0 && m_last_y >= 0)
	{
		int dx = x - m_last_x;
		int dy = y - m_last_y;
		if (std::fabs(dx) != std::fabs(dy) && dx * dy != 0)
			return false;
		const int steps = distance(dx, dy);
		// check the route for obstacles
		dx = sign(dx);
		dy = sign(dy);
		for (int s = 1; s < steps; ++s)
		{
			if (cell(m_last_x + dx * s, m_last_y + dy * s) != Field::Empty)
			{
				return false;
			}
		}
	}
	m_last_x = x;
	m_last_y = y;
	m_field[y * m_width + x] = Field::Done;
	--m_level_remains;
	return true;
}

void
Field::select(int x, int y)
{
	if (x < 0 || x >= m_width || y < 0 || y >= m_height
		|| x == m_sel_x && y == m_sel_y)
	{
		m_sel_x = m_sel_y = -1;
		return;
	}
	if (cell(x, y) == Field::X)
		go(x, y);
	m_sel_x = x;
	m_sel_y = y;
	if (m_level_remains == 0)
	{
		nextLevel();
	}
}

char
Field::convert(Field::CellType value)
{
	return static_cast<char>(value);
}

Field::CellType
Field::cell(int x, int y) const
{
	if (x < 0 || x >= m_width || y < 0 || y >= m_height)
		throw 1;
	return static_cast<Field::CellType>(m_field[y * m_width + x]);
}

void
Field::setCell(int x, int y, CellType value)
{
	if (x < 0 || x >= m_width || y < 0 || y >= m_height)
		throw 1;
	m_field[y * m_width + x] = value;
}

void
Field::fill(Field::CellType value)
{
	for (int x = 0; x < m_width; ++x)
	{
		for (int y = 0; y < m_height; ++y)
		{
			m_field[y * m_width + x] = convert(value);
		}
	}
}

int
_rgb(int c)
{
	return (((c >> 16) & 0xff) << 0) |
		(((c >> 8) & 0xff) << 8) |
		(((c >> 0) & 0xff) << 16);
}

HBRUSH brush_erase = CreateSolidBrush(0x00FFFFFF);
HPEN pen_empty = CreatePen(PS_NULL, 0, 0x00FFFFFF);

HBRUSH brush_default = CreateSolidBrush(_rgb(0xFFFAB4));
HPEN pen_default = CreatePen(PS_SOLID, 1, _rgb(0x695D02));

HBRUSH brush_x = CreateSolidBrush(_rgb(0xED4E32));
HPEN pen_x = CreatePen(PS_SOLID, 1, 0x00334477);

HBRUSH brush_done = CreateSolidBrush(_rgb(0x92C246));
HPEN pen_done = CreatePen(PS_SOLID, 1, 0x335522);

HBRUSH brush_sel = CreateSolidBrush(_rgb(0x4B9FDB));
HPEN pen_sel = CreatePen(PS_SOLID, 1, 0xffffff);

void
selectObjectByCellType(HDC dc, Field::CellType type)
{
	switch (type)
	{
	case Field::Empty:
		SelectObject(dc, brush_default);
		SelectObject(dc, pen_default);
		break;
	case Field::X:
		SelectObject(dc, brush_x);
		SelectObject(dc, pen_x);
		break;
	case Field::Done:
		SelectObject(dc, brush_done);
		SelectObject(dc, pen_done);
		break;
	default:
		throw 1;
		break;
	}
}

void
Field::draw(HDC dc, int width, int height)
{
	m_screen_width = width;
	m_screen_height = height;
	const int dx = width / (m_width);
	const int dy = height / (m_height);
	const int size = m_size = dx > dy ? dy : dx;

	HDC mem_dc;
	HBITMAP mem_bm, old_bm;

	mem_dc = CreateCompatibleDC(dc);
	mem_bm = CreateCompatibleBitmap(dc, width, height);
	old_bm = static_cast<HBITMAP>(SelectObject(mem_dc, mem_bm));

	SelectObject(mem_dc, brush_erase);
	SelectObject(mem_dc, pen_empty);
	Rectangle(mem_dc, 0, 0, width, height);

	for (int x = 0; x < m_width; ++x)
	{
		for (int y = 0; y < m_height; ++y)
		{
			selectObjectByCellType(mem_dc, cell(x, y));
			Rectangle(mem_dc, x * size + 1, y * size + 1,
				(x + 1) * size - 1, (y + 1) * size - 1);
		}
	}
	if (m_sel_x >= 0 && m_sel_y >= 0)
	{
		SelectObject(mem_dc, brush_sel);
		SelectObject(mem_dc, pen_sel);
		Ellipse(mem_dc, m_sel_x * size + 4, m_sel_y * size + 4,
			(m_sel_x + 1) * size - 4, (m_sel_y + 1) * size - 4);
	}

	BitBlt(dc, 0, 0, width, height, mem_dc, 0, 0, SRCCOPY);
	SelectObject(mem_dc, old_bm);
	DeleteObject(mem_bm);
	DeleteDC(mem_dc);
}

void
Field::onMouseDown(int x, int y)
{
	if (m_width <= 0 || m_height <= 0 || m_size <= 0)
		return;
	const int bx = x / m_size;
	const int by = y / m_size;
	select(bx, by);
}

void
Field::again()
{
	reset();
	clearDone();
}

void
Field::clearDone()
{
	for (int x = 0; x < m_width; ++x)
	{
		for (int y = 0; y < m_height; ++y)
		{
			if (cell(x, y) == Field::Done)
			{
				setCell(x, y, Field::X);
			}
		}
	}
}

void
Field::loadLevel(const char *level)
{
	const int width = *level++;
	const int height = *level++;
	allocate(width, height);

	const int x_count = m_level_x_count = *level++;
	for (int i = 0; i < x_count; ++i)
	{
		const int x = *level++ - 'A';
		const int y = *level++ - 'A';
		setCell(x, y, Field::X);
	}

	reset();
	//InvalidateRect(m_game->wnd(), NULL, TRUE);
}

void
Field::loadPredefinedLevel(int num)
{
	if (num < 0 || num >= predefinedLevelsCount)
		throw 1;
	m_level_id = num;
	loadLevel(predefinedLevel[num]);
}

void
Field::nextLevel()
{
	if (m_level_id >= 0 && m_level_id + 1 < predefinedLevelsCount)
	{
		loadPredefinedLevel(m_level_id + 1);
	}
	else
	{
		InvalidateRect(m_game->wnd(), NULL, TRUE);
		randomLevel();
	}
}

const int mininum_width = 6;
const int maximum_width = 12;
const int minimum_height = 6;
const int maximum_height = 14;

const int large_field_threshold = 70;
const int large_field_maximum_count = 14;
const int large_field_minimum_count = 8;
const int small_field_maximum_count = 10;
const int small_field_minimum_count = 6;

int
random_range(int a_min, int a_max)
{
	return a_min + std::rand() % (a_max - a_min);
}

int
Field::getMaxInDirection(int x, int y, int dx, int dy)
{
	int steps = 0;
	while (x + dx * (steps + 1) >= 0 && x + dx * (steps + 1) < m_width &&
		y + dy * (steps + 1) >= 0 && y + dy * (steps + 1) < m_height)
	{
		++steps;
	}
	return steps;
}

void
Field::randomLevel()
{
	m_level_id = -1;
	
	const int width = random_range(mininum_width, maximum_width);
	const int height = random_range(minimum_height, maximum_height);
	allocate(width, height);

	const int count = width * height > large_field_threshold ? 
		random_range(large_field_minimum_count, large_field_maximum_count) :
		random_range(small_field_minimum_count, small_field_maximum_count);
	m_level_x_count = count;

	std::vector<std::pair<int, int> > route(count);

	while (true)
	{
		int x = random_range(0, width);
		int y = random_range(0, height);
		setCell(x, y, Field::X);
		route[0].first = x;
		route[0].second = y;
		for (int s = 1; s < count; ++s)
		{
			while (true)
			{
				const int dx = random_range(-1, 2);
				const int dy = random_range(-1, 2);
				if (dx == 0 && dy == 0)
					continue;
				const int max_steps = getMaxInDirection(x, y, dx, dy);
				if (max_steps == 0)
					continue;
				const int steps = random_range(1, max_steps + 1);
				const int new_x = x + dx * steps;
				const int new_y = y + dy * steps;
				if (go(new_x, new_y))
				{
					route[s].first = new_x;
					route[s].second = new_y;
					break;
				}
			}
		}
		if (validateRoute(route))
		{
			break;
		}
		fill(Field::Empty);
	}
	reset();
}

bool
Field::validateRoute(const std::vector<std::pair<int, int> > &route)
{
	clearDone();
	typedef std::vector<std::pair<int, int> > vector;
	for (vector::const_iterator it = route.begin(); it != route.end(); ++it)
	{
		const int x = it->first;
		const int y = it->second;
		if (cell(x, y) != Field::X || !go(x, y))
		{
			return false;
		}
	}
	
	// fast clear Done
	for (vector::const_iterator it = route.begin(); it != route.end(); ++it)
	{
		const int x = it->first;
		const int y = it->second;
		if (cell(x, y) == Field::Done)
		{
			setCell(x, y, Field::X);
		}
	}
	return true;
}


/*****************************************************************************/
/* ---------------------------------- Game --------------------------------- */
/*****************************************************************************/

Game *g_game = 0;

Game::Game(HINSTANCE inst, HWND wnd):
	m_field(0),
	m_inst(inst),
	m_wnd(wnd)
{
	m_field = new Field(this);
}

Field *
Game::field() const
{
	return m_field;
}

void
Game::onMouseDown(int x, int y)
{
	m_field->onMouseDown(x, y);
	//SendMessage(m_wnd, WM_PAINT, 0, 0);
	InvalidateRect(m_wnd, NULL, TRUE);
}

void
Game::onAgain()
{
	m_field->again();
}

HWND
Game::wnd() const
{
	return m_wnd;
}